package com.ikingtech.framework.sdk.log.embedded.aspect;

import com.fasterxml.jackson.core.type.TypeReference;
import com.ikingtech.framework.sdk.context.security.Identity;
import com.ikingtech.framework.sdk.context.security.Me;
import com.ikingtech.framework.sdk.core.response.R;
import com.ikingtech.framework.sdk.utils.SpEL;
import com.ikingtech.framework.sdk.enums.common.FrameworkAgentTypeEnum;
import com.ikingtech.framework.sdk.log.embedded.annotation.OperationLog;
import com.ikingtech.framework.sdk.log.embedded.properties.LogProperties;
import com.ikingtech.framework.sdk.log.model.rpc.OperationLogReportParam;
import com.ikingtech.framework.sdk.utils.Tools;
import com.ikingtech.framework.sdk.web.support.agent.FrameworkAgentProxy;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.context.MessageSource;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import jakarta.servlet.http.HttpServletRequest;
import java.time.LocalDateTime;
import java.util.Locale;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import static com.ikingtech.framework.sdk.context.constant.SecurityConstants.HEADER_MENU_ID;

/**
 * 操作日志记录处理
 *
 * @author wangbo
 */
@Aspect
@Slf4j
@RequiredArgsConstructor
public class OperationLogAspect {

    private final LogProperties properties;

    private final MessageSource messageSource;

    private ThreadPoolExecutor threadPool;

    public void init() {
        threadPool = new ThreadPoolExecutor(
                2,
                100,
                30L,
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(3000),
                r -> new Thread(r, "operation-log-ThreadPool-" + r.hashCode()),
                (r, executor) -> r.run());
    }

    @AfterReturning(pointcut = "@annotation(operationLog)", returning = "result")
    public void doAfterReturning(JoinPoint joinPoint, OperationLog operationLog, Object result) {
        OperationLogReportParam log = parseReturning(joinPoint, operationLog, result);
        if (null != log) {
            // 保存数据库
            threadPool.execute(() -> this.send(log));
        }
    }

    @AfterThrowing(value = "@annotation(operationLog)", throwing = "e")
    public void doAfterThrowing(JoinPoint joinPoint, OperationLog operationLog, Exception e) {
        OperationLogReportParam log = parseException(joinPoint, operationLog, e);
        if (null != log) {
            // 保存数据库
            threadPool.execute(() -> this.send(log));
        }
    }

    private OperationLogReportParam parseReturning(final JoinPoint joinPoint, OperationLog operationLog, Object proceedResult) {
        Identity me = Me.info();
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (null == requestAttributes) {
            return null;
        }
        HttpServletRequest request = requestAttributes.getRequest();
        OperationLogReportParam log = new OperationLogReportParam();

        // 设置请求方式
        log.setRequestMethod(request.getMethod());

        log.setRequestUrl(request.getRequestURI());

        log.setDomainCode(Me.domainCode());

        log.setTenantCode(Me.tenantCode());

        log.setAppCode(Me.appCode());

        // 设置操作用户
        if (Tools.Str.isBlank(me.getId())) {
            log.setOperateUsername(this.messageSource.getMessage("systemInternalCalls", null, Locale.forLanguageTag(Me.lang())));
        } else {
            log.setOperateUserId(me.getId());
            log.setOperateUsername(Tools.Str.format("{}({})", me.getUsername(), me.getName()));
        }

        // 设置网络信息
        String ip = Tools.Network.ip(request);

        log.setIp(Tools.Network.ip(request));
        if (Boolean.TRUE.equals(properties.getLocationEnable())) {
            log.setLocation(Tools.Network.ipRegion(ip));
        }

        // 设置详细结果
        if (null != proceedResult) {
            R<Object> result = Tools.Json.objToBean(proceedResult, new TypeReference<>() {
            });
            log.setSuccess(result.isSuccess());
            log.setMessage(result.getMsg());
        } else {
            log.setSuccess(true);
            log.setMessage(this.messageSource.getMessage("methodDidNotReturnResult", null, Locale.forLanguageTag(Me.lang())));
        }

        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        if (null != signature) {
            SpEL spEl = SpEL.init(signature.getMethod(), joinPoint.getArgs(), proceedResult);
            String className = joinPoint.getTarget().getClass().getName();
            String methodName = signature.getName();
            log.setMethod(className + "." + methodName);
            String dataId = (String) spEl.parse(operationLog.dataId());
            log.setOperation(Tools.Str.isNotBlank(dataId) ? Tools.Str.format("{}({})", operationLog.value(), dataId) : operationLog.value());
        }

        // 设置请求/响应信息
        log.setModule(request.getHeader(HEADER_MENU_ID));
        if (operationLog.saveRequestData()) {
            log.setRequestParam(Tools.Json.toJsonStr(joinPoint.getArgs()));
        }
        if (operationLog.saveResponseData() && null != proceedResult) {
            log.setResponseBody(Tools.Json.toJsonStr(proceedResult));
        }

        // 设置操作时间
        log.setOperationTime(LocalDateTime.now());

        return log;
    }

    private OperationLogReportParam parseException(final JoinPoint joinPoint, OperationLog operationLog, Exception e) {
        OperationLogReportParam log = this.parseReturning(joinPoint, operationLog, null);
        if (null != log) {
            log.setSuccess(false);
            log.setMessage(e.getClass() + System.lineSeparator() + (null == e.getCause() ? "" : e.getCause().getMessage()) + System.lineSeparator() + e.getMessage());
        }
        return log;
    }

    private void send(OperationLogReportParam logDTO) {
        FrameworkAgentProxy.agent().execute(FrameworkAgentTypeEnum.OPERATION_LOG, logDTO);
    }
}
